package connect4;

import java.util.Collection;
import java.util.Random;


public class MiniMax {

	private final int MAX_DEPTH = 3;
	private final int MAX_VALUE = -6000000;
	private final int MIN_VALUE = 5000000;
	
	/**
	 * finds a move for the player <b>player</b>, when he plays against the
	 * player <b>opponent</b>.
	 * 
	 * @param player
	 *            the current player.
	 * @param opponent
	 *            the player playing against the current player.
	 * @param board
	 *            the current playing board.
	 * @return a move for <b>player</b>
	 */
	public Move findMove(Player player, Player opponent, Board board) {
		int max = this.MAX_VALUE;
		int score;
		Move retMove = null;
		
		for (Move m : board.getPossibleMoves()) {
			Board tempBoard = board.getCopy();
			tempBoard.update(Player.PLAYER_2, m);
			score = this.getMinVal(tempBoard, 0);
			if (score > max) {
				retMove = m;
				max = score;
			}
		}

		return retMove;
	}
	
	/**
	 * Choose the best move for the min player
	 * @param board the board
	 * @return the minimum value
	 */
	private int getMinVal (Board board, int depth) {
		int min = this.MIN_VALUE;
		int score;
		if (board.isGameOver()) {
			return this.scoreFinishedGame(board);
		}
		
		for (Move m : board.getPossibleMoves()) {
			Board tempBoard = board.getCopy();
			tempBoard.update(Player.PLAYER_1, m);
			score = this.getMaxVal(tempBoard, depth + 1);
			
			if (score <= min) {
				min = score;
			}
		}
		
		return min;
	}
	
	/**
	 * Choose the best move for the max player
	 * @param board the board
	 * @return the maximum value
	 */
	private int getMaxVal (Board board, int depth) {
		int max = this.MAX_VALUE;
		int score;
		
		if (board.isGameOver()) {
			return this.scoreFinishedGame(board);
		}
		
		if (depth == this.MAX_DEPTH) {
			return this.scoreHeuristically(board);
		}
		
		for (Move m : board.getPossibleMoves()) {
			Board tempBoard = board.getCopy();
			tempBoard.update(Player.PLAYER_2, m);
			score = this.getMinVal(tempBoard, depth);
			if (score >= max) {
				max = score;
			}
		}
		
		return max;
	}


	/**
	 * scores a finished game. Since the <b>maxPlayer</b> maximizes, the best
	 * score is achieved if he wins and worse score is achieved if he loses. The
	 * score of a drawn game is zero.
	 * 
	 * @param board
	 *            the playing board.
	 * @return the score.
	 */
	private int scoreFinishedGame(Board board) {
		if (board.isWinner(Player.PLAYER_1)) {
			return -600000;
		} else if (board.isWinner(Player.PLAYER_2)) {
			return 500000;
		}
		
		return 0;
	}

	/**
	 * scores a game heuristically. This is used, when the search tree reaches
	 * the maximum depth and the game is still not over.
	 * 
	 * @param board
	 *            the current playing board.
	 * @return the score.
	 */
	private int scoreHeuristically(Board board) {
		int p1;
		int p2;
		int score = 0;
		for (Player[] p : board.getBlocks()) {
			p1 = 0;
			p2 = 0;
			for (int i = 0; i <= 3; i++) {
				if (p[i].getCaption().toString().equalsIgnoreCase(Player.PLAYER_1.getCaption().toString())) {
					p1 = p1 + 1;
				} else if (p[i].getCaption().toString().equalsIgnoreCase(Player.PLAYER_2.getCaption().toString())) {
					p2 = p2 + 1;
				}
			}
			if (p2 == 0) {
				switch (p1) {
				case 0: score = score - 0; break;
				case 1: score = score - 20; break;
				case 2: score = score - 200; break;
				case 3: score = score - 2000; break;
				}
			}
			if (p1 == 0) {
				switch (p1) {
				case 0: score = score + 0; break;
				case 1: score = score + 10; break;
				case 2: score = score + 100; break;
				case 3: score = score + 1000; break;
				}
			}
		
		}
		
		return score;
	}

}
